package com.youlu.campus.service.statistics;

import com.google.common.collect.Lists;
import com.youlu.campus.entity.ActivityChannelStatistics;
import com.youlu.campus.entity.VO.req.ActivityChannelStatisticsPVUVReq;
import com.youlu.campus.entity.statistics.ActivityChannelPageElementStatistics;
import com.youlu.campus.entity.statistics.PageElement;
import com.youlu.campus.entity.statistics.vo.ChannelPageElementReq;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;


import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class ActivityChannelPageElementStatisticsService {

    @Autowired
    private MongoTemplate mongoTemplate;



    @Autowired
    private MapperFacade mapperFacade;

    private static final String USER_ACTIVITY_DATE_UV = "channel:statistics:channel:%s:activity:%s:userId:%s:pageKey" +
            ":%s:date:%s";


    /**
     * 一天时间
     */
    private final static long DAY_EXPIRE_SECOND = 24 * 60 * 60;


    public List<ActivityChannelPageElementStatistics> list(ChannelPageElementReq req){
        String activityId = req.getActivityId();
        Date startTime = req.getStartTime();
        Date endTime = req.getEndTime();
        Query query = new Query();
        query.addCriteria(Criteria.where("channelCode").is(req.getChannelCode()));
        query.addCriteria(Criteria.where("activityId").is(activityId));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) {
            query.addCriteria(Criteria.where("date").gte(Integer.valueOf(sdf.format(startTime))).lte(Integer.valueOf(sdf.format(endTime))));
        }
        if(StringUtils.isNotBlank(req.getPageKey())){
            query.addCriteria(Criteria.where("pageKey").is(req.getPageKey()));
        }
        List<ActivityChannelPageElementStatistics> activityPageStatisticsList = this.mongoTemplate.find(query, ActivityChannelPageElementStatistics.class);
        if (CollectionUtils.isEmpty(activityPageStatisticsList)) {
            return null;
        }
        List<ActivityChannelPageElementStatistics> result = Lists.newArrayList();
        Map<String, List<ActivityChannelPageElementStatistics>> mapPayButton = activityPageStatisticsList.stream().collect(Collectors.groupingBy(ActivityChannelPageElementStatistics::getPageKey));
        mapPayButton.forEach((buttonKey, item) -> {
            Query query1 = new Query();
            query1.addCriteria(Criteria.where("key").is(buttonKey));
            PageElement pageElement = this.mongoTemplate.findOne(query1, PageElement.class);
            if(Objects.nonNull(pageElement)){
                long pv = item.stream().mapToLong(ActivityChannelPageElementStatistics::getPv).sum();
                long uv = item.stream().mapToLong(ActivityChannelPageElementStatistics::getUv).sum();
                Double second = item.stream().mapToDouble(ActivityChannelPageElementStatistics::getSecond).sum();
                ActivityChannelPageElementStatistics pageStatistics = new ActivityChannelPageElementStatistics();
                pageStatistics.setUv(uv);
                pageStatistics.setPv(pv);
                pageStatistics.setPageKey(buttonKey);
                pageStatistics.setPageName(pageElement.getName());
                if (pv > 0 && second.intValue() > 0) {
                    BigDecimal pvBigDecimal = BigDecimal.valueOf(pv);
                    BigDecimal secondBigDecimal = BigDecimal.valueOf(second);
                    pageStatistics.setSecond(secondBigDecimal.divide(pvBigDecimal,2,BigDecimal.ROUND_HALF_UP).doubleValue());
                }
                //答题率
                if("qa-done-button".equals(buttonKey)){
                    BigDecimal uvBig = BigDecimal.valueOf(uv);
                    List<ActivityChannelPageElementStatistics> activityChannelPageElementStatistics = mapPayButton.get("jin/pages/index");
                    if(CollectionUtils.isNotEmpty(activityChannelPageElementStatistics)){
                            long signUPuv = activityChannelPageElementStatistics.stream().mapToLong(ActivityChannelPageElementStatistics::getUv).sum();
                            BigDecimal signUpUVBig = BigDecimal.valueOf(signUPuv);
                            BigDecimal rest = uvBig.divide(signUpUVBig, 2, BigDecimal.ROUND_HALF_UP);
                            pageStatistics.setSignUpRatio(rest.doubleValue());
                    }
                }
                result.add(pageStatistics);
            }
        });
        return result.stream().sorted(Comparator.comparing(ActivityChannelPageElementStatistics::getUv).reversed()).collect(Collectors.toList());
    }

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @Async
    public void channelPageElementStatistics(ActivityChannelStatisticsPVUVReq req){
         String activityId = req.getActivityId();
        String channelCode = req.getChannelCode();
        String clientCookie = req.getClientCookie();
        String pageKey = req.getPageKey();
        Double second = req.getSecond();
        if(StringUtils.isAnyBlank(activityId,channelCode,pageKey,clientCookie)){
            return;
        }
        String nowDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        Integer currentDate = Integer.valueOf(nowDate);
        String dateFormat = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String userActivityButtonKeyDate = String.format(USER_ACTIVITY_DATE_UV, channelCode,activityId, clientCookie, pageKey, currentDate);
        Query query = new Query();
        query.addCriteria(Criteria.where("channelCode").is(channelCode));
        query.addCriteria(Criteria.where("activityId").is(activityId));
        query.addCriteria(Criteria.where("date").is(currentDate));
        query.addCriteria(Criteria.where("pageKey").is(pageKey));
        ActivityChannelPageElementStatistics activityPageStatistics = this.mongoTemplate.findOne(query, ActivityChannelPageElementStatistics.class);
        Update update = new Update();
        // 原子性地“如果不存在则设置，并在首次设置时对 uv+1，同时写入1天过期”
        Boolean isNew = redisTemplate.opsForValue().setIfAbsent(
                userActivityButtonKeyDate,  // key
                "1",                      // value
                Duration.ofDays(1)        // 过期：1天（Redis 2.6.12+ 支持在SET时直接带过期）
        );
        if (Boolean.TRUE.equals(isNew)) {
            // 仅在首次写入时递增 uv（避免重复计数）
            update.inc("uv", 1);
        }
        if (second.intValue() > 0) {
            update.inc("second", second);
        }
        if (second.intValue() == 0) {
            update.inc("pv", 1);
        }
        if (Objects.nonNull(activityPageStatistics)) {
            this.mongoTemplate.updateFirst(query, update, ActivityChannelPageElementStatistics.class);
            return;
        }
        Date now = new Date();
        update.set("dateFormat", dateFormat);
        update.setOnInsert("createdTime", now);
        update.set("updatedTime", now);
        FindAndModifyOptions findAndModifyOptions = new FindAndModifyOptions();
        findAndModifyOptions.upsert(Boolean.TRUE);
        findAndModifyOptions.returnNew(Boolean.TRUE);
        this.mongoTemplate.findAndModify(query, update, findAndModifyOptions, ActivityChannelPageElementStatistics.class);

    }

    /**
     * 一天剩下多少秒
     *
     * @return
     */
    private long dayOfNextTime() {
        LocalTime midnight = LocalTime.MIDNIGHT;
        LocalDate today = LocalDate.now();
        LocalDateTime todayMidnight = LocalDateTime.of(today, midnight);
        LocalDateTime tomorrowMidnight = todayMidnight.plusDays(1);
        return TimeUnit.NANOSECONDS.toSeconds(Duration.between(LocalDateTime.now(), tomorrowMidnight).toNanos());
    }



}
